/* eslint-disable max-classes-per-file */
/*
 * @Description:功能配置事件处理
 *              所有功能配置事件统一处理类
 *              * 调用sp
 *              * 系统策略
 *              * 第三方系统web请求
 *              * 一个典型的web请求在功能《公用API》的配置 见页尾
 * @Version: 2.0
 * @Autor: wiz
 * @Date: 2023-11-08 14:54:19
 * @LastEditors: swc
 * @LastEditTime: 2024-11-28 11:08:01
 */
import Vue from 'vue';
import { I_queryRes } from '../../arg/queryRes';
import { I_apiReqOption, T_data } from '../data';
import { I_return, T_return } from '../return';

import { I_event, I_funInfo, I_userInfo, T_event } from '../../arg/event';
import { I_setDataRes } from '../../arg/setDataRes';

// 当调用第三方数据时，返回的属性与本地属性对应有用
// 在接收时sourceField 为返回源或者path，targetField 为对应本地字段
// 在请求时sourceField 为对应本地字段或者path，targetField 为第三方请求字段
export interface I_extWebDataMap {
  // 源属性 属性名，可是一个json path  a.b.c
  sourceField: string;
  // 目标属性 可是一个json path  a.b.c
  targetField: string;
  // 数据类型
  dataType: string;
}
// 当接收到外部api返回的字段对应
export interface I_extWebDataHandle {
  dataMap: Array<I_extWebDataMap>;
}
export interface I_extWebReq {
  url: string;
  method: 'post' | 'get';
  params: any; // 请求体模板，变量部分值为null
  options: I_apiReqOption;
  // 数据处理
  dataHandle: I_extWebDataHandle;
}
export interface I_extWebResp {
  successField: string;
  successValue: Array<boolean | number>;
  // 数据处理
  dataHandle: I_extWebDataHandle;
}

export interface I_openUrlReq {
  key: string;
  funName: string;
  field: string;
}

// 外部web请求配置，它来自功能【公用Api】的内容字段
export class T_extWebApiReqConf {
  // 请求参数
  protected req: I_extWebReq;

  // 返回参数
  protected resp: I_extWebResp;

  // api 选项 请求头信息
  protected options: I_apiReqOption;

  constructor() {
    this.resp = {
      successField: '',
      successValue: [],
      dataHandle: { dataMap: [] }
    };
  }

  public getReq(): I_extWebReq {
    return this.req;
  }

  public getResp(): I_extWebResp {
    return this.resp;
  }

  public getUrl(): string {
    return this.req.url;
  }

  public getParams(): any {
    return this.req.params;
  }

  public getWebOptions(): I_apiReqOption {
    return this.options;
  }

  public getMethod(): string {
    return this.req.method;
  }

  public getReqDataHandle(): I_extWebDataHandle {
    return this.req.dataHandle;
  }

  public methodIsPost(): boolean {
    return this.req.method === 'post';
  }

  public methodIsGet(): boolean {
    return this.req.method === 'get';
  }

  // 设定 公用API web请求内容
  public setApiContent(p: any): I_return {
    if (!p?.req || !p.resp) {
      return new T_return('-507,FE,90');
    }
    const r = this.setReq(p.req);
    if (r.isErr()) {
      return r;
    }
    const r1 = this.setResp(p.resp);
    if (r1.isErr()) {
      return r1;
    }
    if (p?.req?.options) {
      p.req.options['isExternalReq'] = true;
      this.setWebApiOptions(p.req.options);
    }
    return new T_return();
  }

  // 失败返回false
  private chkReq(p: I_extWebReq): boolean {
    // return !p?.method && (p.method === 'get' ||  p.method === 'post') && !p?.url
    return p?.method && (p.method === 'get' || p.method === 'post') && !!p?.url;
  }

  // 失败返回false
  private chkResp(p: I_extWebResp): boolean {
    return (
      // !p?.dataHandle &&
      // Array.isArray(p.dataHandle) &&
      // p.dataHandle.length === 0 &&
      // !p?.successField &&
      // !p?.successValue &&
      // Array.isArray(p.successValue) &&
      // p.successValue.length === 0
      p?.dataHandle &&
      Array.isArray(p.dataHandle.dataMap) &&
      p.dataHandle.dataMap.length !== 0 &&
      p?.successField &&
      p?.successValue &&
      Array.isArray(p.successValue) &&
      p.successValue.length !== 0
    );
  }

  // 检查api req参数，它可以是一个Json字符串
  private setReq(p: I_extWebReq | string): I_return {
    const r = new T_return();
    if (typeof p === 'string') {
      const _p = JSON.parse(p) as I_extWebReq;
      if (!this.chkReq(_p)) {
        r.errmsg = `-507,FE,90`;
        return r;
      }
      this.req = _p;
    } else {
      if (!this.chkReq(p)) {
        r.errmsg = `-507,FE,90`;
        return r;
      }
      this.req = p;
    }

    return r;
  }

  // 检查api resp参数，它可以是一个Json字符串
  private setResp(p: I_extWebResp | string): I_return {
    const r = new T_return();
    if (typeof p === 'string') {
      const _p = JSON.parse(p) as I_extWebResp;
      if (!this.chkResp(_p)) {
        r.errmsg = `-507,FE,90`;
        return r;
      }
      this.resp = _p;
    } else {
      if (!this.chkResp(p)) {
        r.errmsg = `-507,FE,90`;
        return r;
      }
      this.resp = p;
    }
    return r;
  }

  private setWebApiOptions(p: I_apiReqOption): void {
    this.options = p;
  }
}
export interface I_extWebApiReqConf extends T_extWebApiReqConf {}

// 事件处理
// new T_eventHandle(apiName) 如果不在此传入apiName 则必须在调用doEvent方法前通过setApiName()来设置apiName
export class T_eventHandle extends T_data {
  private apiName: string;

  public reqConf: I_extWebApiReqConf = new T_extWebApiReqConf();

  // 传入动作指令 公用API name
  // 如果不在此传入apiName 则必须在调用doEvent方法前通过setApiName()来设置apiName
  constructor(apiName?: string) {
    super();
    this.apiName = apiName;
  }

  public setApiName(p: string): T_eventHandle {
    this.apiName = p;
    return this;
  }

  /**
   * @Author: wiz
   * @param {I_userInfo} userInfo
   * @param {I_funInfo} funInfo
   * @param {Function} data 获取数据的回调方法，
   *                   通常用在 第三方web请求时获取请求参数的源数据池，它是一个对象。
   * @param {boolean} isShowMsg //是否显示正常执行中
   * @return {Promise<I_return<{ apiType: number; res: any }>>}
   * @description: 执行事件
   */
  public async doEvent(
    userInfo: I_userInfo,
    funInfo: I_funInfo,
    data: () => { [key: string]: any },
    isShowMsg: boolean = true
  ): Promise<I_return<{ apiType: number; res: I_setDataRes }>> {
    let msg = null;
    if (isShowMsg) {
      msg = Vue.prototype.$Message.loading({
        content: `${Vue.prototype.$srv.getI18nByPath(
          'others.beingexecuted'
        )} ...`,
        duration: 0
      });
    }

    let apiType: number = -1; // 0 存储过程  1 系统策略 2 规则库 3 web请求 4 逻辑服务
    try {
      const currApiName = this.apiName;
      // 获取api 明细
      const [b, res, rows, err] = await this.getApiDet(this.apiName);
      if (!err || !b) {
        throw new Error(err);
      }
      if (rows.length > 0) {
        apiType = rows[0].f_type;
        const r = new T_return();
        const content = JSON.parse(rows[0].content);
        if (apiType === 0) {
          const [_res, _err] = await this.doExecSp(
            currApiName,
            userInfo,
            funInfo,
            data
          );
          if (_err || !_res.success) {
            r.errmsg = _err ?? _res.msg;
          } else {
            r.setReturnData('conf', {
              apiType,
              res: _res
            });
          }
          return r;
        }
        if (apiType === 1) {
          const [_res, _err] = await this.doExecSvcLogic(
            currApiName,
            userInfo,
            funInfo,
            data
          );
          if (_err || !_res.success) {
            r.errmsg = _err ?? _res.msg;
          } else {
            r.setReturnData('conf', {
              apiType,
              res: _res
            });
          }
          return r;
        }
        if (apiType === 2) {
          const [_res, _err] = await this.doExecRuleLib(
            currApiName,
            userInfo,
            funInfo,
            data
          );
          if (_err || !_res.success) {
            r.errmsg = _err ?? _res.msg;
          } else {
            r.setReturnData('conf', {
              apiType,
              res: _res
            });
          }
          return r;
        }
        if (apiType === 3) {
          if (content?.openUrl) {
            // const resp = this.openUrl(content.openUrl, data());
            const resp = this.openUrl(content, data());
            r.errmsg = resp.errmsg;
            r.setReturnData('conf', {
              apiType,
              res: {
                success: true,
                msg: '200,FE,90',
                data: resp.getReturnData('conf')
              }
            });
            return r;
          }
          if (rows[0].content) {
            // const _r = this.reqConf.setApiContent(rows[0].content);
            const _r = this.reqConf.setApiContent(JSON.parse(rows[0].content));
            if (_r.isErr()) {
              r.errmsg = _r.errmsg;
              return r;
            }
            const [_res, _err] = await this.doExecExtWebReq(this.reqConf, data);
            // if (_err || !_res.success) {
            if (_err) {
              r.errmsg = _err ?? _res.msg;
            } else {
              r.setReturnData('conf', {
                apiType,
                res: this.getExtWebResp(this.reqConf.getResp(), [_res, _err]),
                data: {
                  reqParams: this.reqConf.getReq()
                }
              });
            }
            return r;
          }
        }
        if (apiType === 4) {
          const [_res, _err] = await this.doExecSysRule(
            currApiName,
            userInfo,
            funInfo,
            data
          );
          if (_err || !_res.success) {
            r.errmsg = _err ?? _res.msg;
          } else {
            r.setReturnData('conf', {
              apiType,
              res: _res
            });
          }
          return r;
        }
      }
    } catch (er) {
      throw new Error(er);
    } finally {
      if (isShowMsg) {
        msg();
      }
    }
    return new T_return('-50008,FE,90'); // 无法识别的关键字
  }

  // 公用API name 获取明细 {类型 内容}
  protected getApiDet(
    ApiName: string
  ): Promise<[boolean, I_queryRes<any>, any[], string]> {
    return this.getDataWithOption(
      this.getSysDefLang(),
      this.lexicon.globalVar.dbType.MYSQL,
      this.lexicon.globalVar.tableName.PUB_API,
      ['f_name', 'f_type', 'content'],
      { f_name: { $eq: ApiName } }
    );
  }

  private async doExecSp(
    spName: string,
    userInfo: I_userInfo,
    funInfo: I_funInfo,
    data: () => { [key: string]: any }
  ): Promise<[I_setDataRes, string]> {
    const p = new T_event(userInfo, funInfo);
    p.data = data();
    p.type = 'sp';
    p.cmd = spName;
    try {
      return await this.standardPostReq<I_setDataRes, I_event>(p);
    } catch (er) {
      throw Error(er);
    }
  }

  private async doExecSvcLogic(
    sysCmd: string,
    userInfo: I_userInfo,
    funInfo: I_funInfo,
    data: () => { [key: string]: any }
  ): Promise<[I_setDataRes, string]> {
    const p = new T_event(userInfo, funInfo);
    p.data = data();
    p.type = 'svcLogic';
    p.cmd = sysCmd;
    try {
      return await this.standardPostReq<I_setDataRes, I_event>(p);
    } catch (er) {
      throw Error(er);
    }
  }

  private async doExecSysRule(
    sysCmd: string,
    userInfo: I_userInfo,
    funInfo: I_funInfo,
    data: () => { [key: string]: any }
  ): Promise<[I_setDataRes, string]> {
    const p = new T_event(userInfo, funInfo);
    p.data = data();
    p.type = 'sysRule';
    p.cmd = sysCmd;
    try {
      return await this.standardPostReq<I_setDataRes, I_event>(p);
    } catch (er) {
      throw Error(er);
    }
  }

  private async doExecRuleLib(
    ruleName: string,
    userInfo: I_userInfo,
    funInfo: I_funInfo,
    data: () => { [key: string]: any }
  ): Promise<[I_setDataRes, string]> {
    const p = new T_event(userInfo, funInfo);
    p.data = data();
    p.type = 'ruleLib';
    p.cmd = ruleName;
    try {
      return await this.standardPostReq<I_setDataRes, I_event>(p);
    } catch (er) {
      throw Error(er);
    }
  }

  private async doExecExtWebReq(
    req: I_extWebApiReqConf,
    data: () => { [key: string]: any }
  ): Promise<[any, string]> {
    try {
      let r: [any, string];
      if (req.methodIsPost()) {
        r = await this.standardPostReq(
          this.getExtWebReqParams(req, data),
          req.getUrl(),
          req.getWebOptions()
        );
      }
      if (req.methodIsGet()) {
        // r = await this.standardPostReq(
        r = await this.standardGetReq(
          this.getExtWebReqParams(req, data),
          req.getUrl(),
          req.getWebOptions()
        );
      }
      return r;
    } catch (er) {
      throw Error(er);
    }
  }

  private getExtWebReqParams(
    req: I_extWebApiReqConf,
    data: () => { [key: string]: any }
  ): any {
    const d = data().data;
    const param = req.getParams();
    const reqDataMap = req.getReqDataHandle().dataMap;
    for (const k of Object.keys(d)) {
      // 找对应
      const reqDmap = reqDataMap.filter(el => el.sourceField === k);

      if (reqDmap.length > 1) {
        throw Error('请求KEY对象属性存在重复,可设置PATH为key');
      }
      // 没有找到映射
      if (this.utils.noValue(reqDmap)) {
        continue;
      }
      if (!this.utils.setObjValByPath(param, reqDmap[0].targetField, d[k])) {
        throw Error('测试请求参数失败');
      }
    }
    return param;
  }

  private getExtWebResp(
    respConf: I_extWebResp,
    p: [any, string]
  ): [I_setDataRes, string] {
    const [res, err] = p;
    if (err) {
      return [{ success: false, data: null, msg: null }, err];
    }
    if (this.utils.noValue(res[respConf?.successField])) {
      throw Error('未得到预期的成功返回字段');
    }
    if (this.utils.noValue(respConf?.successValue)) {
      throw Error('未得到预期的成功返回字段值');
    }
    for (const o of respConf.successValue) {
      if (res[respConf.successField] === o) {
        return [
          {
            success: true,
            data: this.getExtWebRespData(res, respConf.dataHandle),
            msg: null
          },
          err
        ];
      }
    }
    return [{ success: false, data: null, msg: null }, err];
  }

  private getExtWebRespData(res: any, dh: I_extWebDataHandle): any {
    const d: any = {};
    dh.dataMap.forEach(el => {
      this.utils.set(d, el.targetField, res[el.sourceField]);
    });
    return d;
  }

  // {"openUrl": "http://47.113.200.153:30579/editor/index.html?id=${pl_hang_no}"}
  private openUrlOld(
    urlContent: string,
    data: any
  ): I_return<{ openUrl: string }> {
    const r = new T_return();
    r.errmsg = '-119,FE,90';
    r.setReturnData('conf', { openUrl: '' });
    const funName: string = data.funName;
    const rows: Array<any> = data?.data?.rows[funName] || [];

    if (rows.length === 0) {
      return r;
    }
    const regex = /\$\{([^}]*)\}/g;
    const matches = urlContent.matchAll(regex);
    const result = Array.from(matches, match => match[1]);
    result.forEach((btnTitle, idx) => {
      for (const o of rows) {
        if (o[btnTitle]) {
          result[idx] = o[btnTitle];
        }
      }
    });

    const pattern = /\${.*?}/g; // 建立可以全局寻找的匹配模式
    let i = 0;

    urlContent = urlContent.replace(pattern, function() {
      return result[i++];
    });
    r.setReturnData('conf', { openUrl: urlContent });
    r.errmsg = '';
    return r;
  }

  private openUrl(content: any, data: any): I_return<{ openUrl: string }> {
    const r = new T_return();

    if (content?.params) {
      // 后缀参数
      const urlContent = [];
      const params: I_openUrlReq[] = content.params;
      for (const el of params) {
        const rows = data?.data?.rows[el.funName] || [];
        if (rows.length === 0) {
          r.errmsg = '-119,FE,90';
          r.setReturnData('conf', { openUrl: '' });
          return r;
        }

        const obj = rows.find(row => row[el.field]);
        if (obj) {
          urlContent.push(`${el.key}=${obj[el.field]}`);
        }
      }
      const urlContentStr =
        content.openUrl.indexOf('?') > 0
          ? `${content.openUrl}&${urlContent.join('&')}`
          : `${content.openUrl}?${urlContent.join('&')}`;
      r.setReturnData('conf', {
        openUrl: urlContentStr
      });
    } else {
      // 无需后缀参数直接返回
      r.setReturnData('conf', { openUrl: content.openUrl });
    }
    r.errmsg = '';
    return r;
  }
}
export interface I_eventHandle extends T_eventHandle {}

/** 第三方web请求 content字段样例
 {
  "req": {
    "url": "http://xxx.xxx.xxx.xxx:port/path",  
    "method": "post",
        "params":{
            "a":null,
            "b":"xxx"
        },
    "options": {
      "headers": {                       //非必需 特定请求头
        "Content-Type": "application/json; charset=utf-8",
        "Authorization":"Basic keykdkdkkaadsf"
      }
     
    },
    "dataHandle": {
      "dataMap": [{
        "sourceField": "xxx",      //本地的字段 或者path
        "targetField": "a",      // 对应请求目标字段 可以是一个json path
        "dataType": "string"       //数据类型
      }]
    }
  },
  "resp": {
    "successField": "code",           //返回数据成功字段名
    "successValue": [1, "ok"],        //返回数据成功值 
    "dataHandle": {
      "dataMap": [{
        "sourceField": "xxx",   // 对应请求目标字段 可以是一个json path
        "targetField": "xxx",  //本地的字段 或者path
        "dataType": "string"   //数据类型
      }]
    }
  }
} 
 */
